package com.gitee.huxing.course.netty.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Description: netty客户端
 * @ProjectName: JavaCourse
 * @Package: com.gitee.huxing.course.netty.client
 * @ClassName: NettyClient
 * @Author: huxing
 * @DateTime: 2021-08-19 下午12:25
 */
public class  NettyClient {

    /** ip地址 **/
    private final String host;

    /** ip端口 **/
    private final int port;

    /** Channel **/
    private Channel channel;

    /** 转发消息渠道 **/
    private ChannelHandlerContext context = null;

    /** 判断是否需要重连 **/
    private volatile boolean isChannelPrepared_;

    /** 工作线程组 **/
    private final EventLoopGroup group = new NioEventLoopGroup(1);

    /** 客户端线程组 **/
    private final Bootstrap bootstrap  = new Bootstrap();

    /** 客户端线程池 **/
    volatile public static ConcurrentHashMap<String, NettyClient> channelConcurrent = new ConcurrentHashMap<>();

    /** 网关相应线程池 **/
    volatile public static ConcurrentHashMap<String, ChannelHandlerContext> channelContext = new ConcurrentHashMap<>();

    public NettyClient(String host, int port){
        this.host = host;
        this.port = port;
    }

    public NettyClient(String host, int port, ChannelHandlerContext context){
        this.host = host;
        this.port = port;
        this.context = context;
    }

    /**
     * @Description: 启动netty客户端
     * @Author: huxing
     * @param
     * @Date: 2021/8/19 下午1:44
     **/
    public void start() throws Exception{
        // 判断是否需要重连
        bootstrap.group(group).channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        System.out.println("正在连接中...");
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        // TODO: 客户端实现http请求 不能 不能 一定不能用 HttpServerCodec
                        // TODO: 要手动定义请求的处理模式
                        // 这里把http的请求报文做解码处理，原因是我送的是httpRequest?
                        // 这里修改为客户端编码格式
                        pipeline.addLast(new HttpClientCodec());
                        pipeline.addLast(new HttpObjectAggregator(1024 * 1024));
                        pipeline.addLast(new HttpClientHandler(getInstance()));
                    }
                });
        // 保存ChannelContext
        this.setContext(context);
        // 发起异步请求, 绑定端口号进行处理
        ChannelFuture future = bootstrap.connect(host, port).sync();
        // 绑定监听处理
        future.addListener((ChannelFutureListener) channelFuture -> {
            if (future.isSuccess()) {
                System.out.println("连接服务器成功");
                isChannelPrepared_ = true;
            } else {
                System.out.println("连接服务器失败");
                isChannelPrepared_ = false;
                future.cause().printStackTrace();
                group.shutdownGracefully(); //关闭线程组
            }
        });
        this.channel = future.channel();
    }

    /**
     * @Description: 绑定
     * @Author: huxing
     * @param
     * @return io.netty.channel.Channel
     * @Date: 2021/8/22 下午12:15
     **/
    public Channel getChannel(){
        return channel;
    }

    public ChannelHandlerContext getContext() {
        return context;
    }

    public void setContext(ChannelHandlerContext context) {
        this.context = context;
    }

    public NettyClient getInstance(){
        return this;
    }

    /** 定义一个锁 **/
    public final static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * @Description: 来创建一个随机数
     * @Author: huxing
     * @param host      ip地址
     * @param port      端口号
     * @param random    随机数
     * @param context   channelContext
     * @return com.gitee.huxing.course.netty.client.NettyClient
     * @Date: 2021/8/23 上午11:43
     **/
    public static NettyClient build(String host, int port, int random,
                                                 ChannelHandlerContext context) throws Exception{
        // key参数
        String key = host + ":::" + port + "--" + random;
        lock.readLock().lock();
        try {
            // 尝试获取已创建的值
            NettyClient  client = channelConcurrent.get(key);
            if (client == null) {
                try {
                    // 释放读锁
                    lock.readLock().unlock();
                    // 加写锁
                    lock.writeLock().lock();
                    // 重新创建一个
                    client = new NettyClient(host, port, context);
                    // 保存client
                    channelConcurrent.put(key, client);
                    // 启动客户端
                    client.start();
                } finally {
                    // 释放写锁
                    lock.writeLock().unlock();
                    // 加读锁
                    lock.readLock().lock();
                }
            }
            // 保存context
            client.setContext(context);
            // 保存到列表
            return client;
        } finally {
            lock.readLock().unlock();
        }
    }
}
